/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

import { HvigorLogger } from '../../log/hvigor-log.js';
import { MetricFactory } from '../metric-factory.js';
import { MetricService } from '../metric-service.js';

import { BaseEvent, EventAdditional, EventBody, EventHead, MetricEventType } from './base-event.js';
import { ContinualEvent } from './continual-event.js';
import { LogEvent, MetricLogType } from './log-event';

export enum DurationEventState {
  CREATED = 'created',
  BEGINNING = 'beginning',
  RUNNING = 'running',
  FAILED = 'failed',
  SUCCESS = 'success',
  WARN = 'warn',
}

const MODULE_KEY_SEP = ':';
const TARGET_KEY_SEP = '@';

class DurationEventAdditional implements EventAdditional {
  category?: string;
  parent?: string;
  children: string[];
  state: DurationEventState;
  logId?: string;
  detailId?: string;
  targetName: string;
  moduleName: string;
  taskRunReasons: string[];

  constructor(eventName: string, category: string) {
    this.children = [];
    this.state = DurationEventState.CREATED;
    this.targetName = '';
    this.moduleName = '';
    const indexOfModuleKey = eventName.indexOf(MODULE_KEY_SEP);
    if (indexOfModuleKey > 0) {
      this.moduleName = eventName.substring(0, indexOfModuleKey);
      const indexOfTargetKey = eventName.indexOf(TARGET_KEY_SEP);
      if (indexOfTargetKey > 0) {
        this.targetName = eventName.substring(indexOfModuleKey + 1, indexOfTargetKey);
      }
    }
    this.category = category;
    this.taskRunReasons = [];
  }
}

export class DurationEvent extends BaseEvent {
  additional: DurationEventAdditional;
  log: HvigorLogger = HvigorLogger.getLogger('DurationEvent');

  constructor(id: string, name: string, description: string, pid: number, group: string, tid: string) {
    super(new EventHead(id, name, description, MetricEventType.DURATION), new EventBody(pid, tid));
    this.additional = new DurationEventAdditional(name, group);
  }

  start(state = DurationEventState.RUNNING, time?: number) {
    this.setState(state);
    super.setStartTime(time);
    return this;
  }

  stop(state = DurationEventState.SUCCESS, time?: number) {
    if (
      this.additional.state === DurationEventState.FAILED ||
      this.additional.state === DurationEventState.SUCCESS ||
      this.additional.state === DurationEventState.WARN
    ) {
      return this;
    }
    this.body.endTime = time ?? Number(process.hrtime.bigint());

    const service = MetricService.getInstance();
    this.setState(state);
    for (const childId of this.additional.children) {
      const child = service.getEventById(childId);
      if (!child) {
        this.log.warn(`Can not getEventById:'${childId}' from MetricCacheService.`);
        continue;
      }
      if (!(child instanceof DurationEvent)) {
        this.log.warn(`Child:'${childId}' is not of type DurationEvent.`);
        continue;
      }
      child.stop(state);
    }
    return this;
  }

  setState(state: DurationEventState) {
    this.additional.state = state;
  }

  createSubEvent(name: string, description: string) {
    const child = MetricFactory.createDurationEvent(name, description, '');
    child.setParent(this.getId());
    this.addChild(child.getId());
    return child;
  }

  addChild(childId: string) {
    this.additional.children.push(childId);
  }

  setParent(parentId: string) {
    this.additional.parent = parentId;
  }

  getParent() {
    return this.additional.parent;
  }

  getChildren() {
    return this.additional.children;
  }

  setLog(name?: string, type: MetricLogType = MetricLogType.INFO, description?: string, totalTime?: number) {
    const logEvent = MetricFactory.createLogEvent(name ?? this.head.name, type, this.getTid(), description);
    logEvent.setDurationId(this.getId());
    this.additional.logId = logEvent.getId();
    logEvent.setStartTime(this.body.startTime);
    logEvent.setEndTime(this.body.endTime);
    if (totalTime) {
      logEvent.setTotalTime(totalTime);
    }

    this.setParentLog(logEvent);
    this.setChildrenLog(logEvent);
  }

  setParentLog(logEvent: LogEvent) {
    const parentEvent = MetricService.getInstance().getEventById(this.additional.parent);
    if (parentEvent instanceof DurationEvent) {
      const parentLog = MetricService.getInstance().getEventById(parentEvent.additional.logId);
      if (parentLog instanceof LogEvent) {
        parentLog.addChild(logEvent.getId());
        logEvent.setParent(parentLog.getId());
      }
    }
  }

  setChildrenLog(logEvent: LogEvent) {
    this.additional.children.forEach((child) => {
      const childEvent = MetricService.getInstance().getEventById(child);
      if (childEvent instanceof DurationEvent || childEvent instanceof ContinualEvent) {
        logEvent.addChild(childEvent.additional.logId);
        const childLog = MetricService.getInstance().getEventById(childEvent.additional.logId);
        if (childLog instanceof LogEvent) {
          childEvent.setParentLog(childLog);
        }
      }
    });
  }

  setDetail(name: string) {
    const logEvent = MetricFactory.createLogEvent(name, MetricLogType.DETAIL, this.getTid());
    logEvent.setDurationId(this.getId());
    this.additional.detailId = logEvent.getId();
  }

  setCategory(category: string) {
    this.additional.category = category;
  }

  addTaskRunReason(taskRunReason: string) {
    this.additional.taskRunReasons.push(taskRunReason);
  }
}
